#include "dominion.h"
#include "dominion_helpers.h"
#include "interface.h"
#include "rngs.h"
#include "testutils.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/time.h>
#include <time.h>
#include <assert.h>
#include <getopt.h>
#include <string.h>
#include <stdarg.h>

// For appending lines together into a single log entry string
static char turn_log[16384];
static bool log_printed = false;
static bool verbose = false;


void log_print (void)
{
    if (!log_printed)
        printf ("\n%s", turn_log);
    log_printed = true;
}

void log_flush (void)
{
    if (verbose)
        log_print ();
    memset (turn_log, 0, sizeof(turn_log));
    log_printed = false;
}

void fail_printf (const char *fmt, ...)
{
    va_list args;
    
    log_print ();
    
    va_start (args, fmt);
    vprintf (fmt, args);
    va_end (args);
    
    log_flush ();
}

void pass_printf (const char *fmt, ...)
{
    va_list args;
    
    va_start (args, fmt);
    vsprintf (strchr (turn_log, '\0'), fmt, args);
    va_end (args);
}

// Verifies that the game state matches the expected game state.
static bool verifyGameState_failed;
void verifyGameState_fromFunc ( int playerNum, struct gameState *state,
                                const expectedState_t *expected,
                                const char *msg, const char *funcname )
{
    verifyGameState_failed = true;
    
    for (int card = 0; card <= treasure_map; card++)
    {
        char name[MAX_STRING_LENGTH];
        cardNumToName (card, name);
        
        int count = fullDeckCount (playerNum, card, state);
        
        if (expected->isOfInterest[card] || expected->count[card] != count)
        {
            assert_equal_fromfunc ( expected->count[card], count, funcname,
                                    "verifying number of %s cards "
                                    "outside of played pile, %s",
                                    name, msg );
        }
        
        int playedCount = 0;
        for (int i = 0; i < state->playedCardCount; i++)
        {
            if (state->playedCards[i] == card)
                playedCount++;
        }
        
        if ((expected->isOfInterest[card] && expected->playedCount[card]) || expected->playedCount[card] != playedCount)
        {
            assert_equal_fromfunc ( expected->playedCount[card], playedCount,
                                    funcname,
                                    "verifying number of %s cards "
                                    "in played pile, %s",
                                    name, msg );
        }
    }
    
    int numInvalidCards = fullDeckCount (playerNum, -1, state);
    assert_equal_fromfunc ( numInvalidCards, 0, funcname,
                            "verifying no -1 cards %s", msg );
    
    verifyGameState_failed = false;
}

// Wrapper around playCard which makes a few assertions for you.
// Don't call this directly, use the testPlayCard macro instead.
void testPlayCard_fromFunc (    int handPos, int choice1, int choice2,
                                int choice3, struct gameState *state,
                                expectedState_t *expected,
                                bool expect_fail, const char *funcname )
{
    // If the assert_*_fromfunc macros fail and cause a return before the
    // end of this function, the testPlayCard macro will propagate the return
    // to the calling function.
    testPlayCard_fail = true;
    
    struct gameState statebak;
    char name[MAX_STRING_LENGTH];
    int card = state->hand[state->whoseTurn][handPos];
    
    memcpy (&statebak, state, sizeof(struct gameState));
    cardNumToName (card, name);
    
    expected->count[card]--;
    expected->playedCount[card]++;
    
    int errcode = playCard (handPos, choice1, choice2, choice3, state);
    
    const char *adverb = "correctly";
    int expected_errcode = 0;
    if (expect_fail)
    {
        adverb = "incorrectly";
        expected_errcode = -1;
    }
    
    assert_equal_fromfunc ( errcode, expected_errcode, funcname,
                            "playing a %s %s", name, adverb );
    
    // If we expected this playCard operation to fail, then we first insure
    // that it had no effect on the game state, then we abort the unit test.
    if (expect_fail)
    {
        int cmp_result = memcmp (&statebak, state, sizeof(struct gameState));
        assert_equal_fromfunc ( cmp_result, 0, funcname,
                                "verifying that a %s played %s has no effect",
                                name, adverb );
        log_flush ();
        return;
    }
    
    verifyGameState_fromFunc (  state->whoseTurn, state, expected,
                                "after playing card", funcname );
    if (verifyGameState_failed)
        return;
    
    // No need to force the calling function to return, all the asserts
    // passed.
    testPlayCard_fail = false;
}

// Wrapper around endTurn which makes a few assertions for you.
// Don't call this directly, use the testEndTurn macro instead.
void testEndTurn_fromFunc ( struct gameState *state,
                            expectedState_t *expected, const char *funcname )
{
    testEndTurn_fail = true;
    
    int player = state->whoseTurn;
    
    endTurn (state);
    
    for (int card = 0; card <= treasure_map; card++)
    {
        expected->count[card] += expected->playedCount[card];
        if (expected->playedCount[card] == 0)
            expected->isOfInterest[card] = false;
        expected->playedCount[card] = 0;
    }
    
    verifyGameState_fromFunc (  player, state, expected,
                                "after ending turn", funcname );
    if (verifyGameState_failed)
        return;
    
    testEndTurn_fail = false;
}

// Set the description phrases for the "needed cards" given as arguments
void set_descriptions ( int num_needed_cards,
                        const needed_card_t *needed_cards, 
                        carddesc_t desc )
{
    for (int i = 0; i < num_needed_cards; i++)
    {
        int card = needed_cards[i].cardtype;
        int position = needed_cards[i].position;
        
        char name[MAX_STRING_LENGTH];
        cardNumToName (card, name);
        
        const char *article = "a";
        if (strchr ("AEIOU", name[0]) != NULL)
            article = "an";
        
        desc[i][DESCRIPTION_SZ-1] = '\0';
        if (position < 0)
            snprintf (  desc[i], DESCRIPTION_SZ-1,
                        "%s %s", article, name );
        else
            snprintf (  desc[i], DESCRIPTION_SZ-1,
                        "%s %s in position %d", article, name, position );
    }
}

void run_cardtest (playargs_t arg_ranges, cardtest_f test_callback)
{
    needed_card_t nc[MAX_PLAYARGS];
    int num_nc = 0;
    int handsize = 5;
    
    for (int i = 0; i < MAX_PLAYARGS; i++)
    {
        if (arg_ranges[i].argtype != arg_num)
            num_nc++;
        if (arg_ranges[i].argtype == arg_handcard)
            handsize++;
    }
    
    int num_tests = 1;
    
    for (int i = 0; i < MAX_PLAYARGS; i++)
    {
        num_tests *= arg_ranges[i].arg_max + 1 - arg_ranges[i].arg_min;
        if (arg_ranges[i].argtype == arg_handcard)
            num_tests *= handsize;
    }
    
    printf ("%d tests to run\n", num_tests);
    
    for (int testnum = 0; testnum < num_tests; testnum++)
    {
        int testnum2 = testnum;
        int nc_idx = 0;
        int args[MAX_PLAYARGS];
        int cardtypes[MAX_PLAYARGS];
        
        for (int i = 0; i < MAX_PLAYARGS; i++)
        {
            int range = arg_ranges[i].arg_max + 1 - arg_ranges[i].arg_min;
            if (arg_ranges[i].argtype == arg_handcard)
            {
                int pos = testnum2 % handsize;
                testnum2 /= handsize;
                for (int j = 0; j < nc_idx; j++)
                {
                    if (nc[j].position == pos)
                        goto skip_test;
                }
                nc[nc_idx].position = args[i] = pos;
                nc[nc_idx].cardtype = cardtypes[i] = testnum2 % range + arg_ranges[i].arg_min;
                nc_idx++;
                testnum2 /= range;
            }
            else
            {
                args[i] = testnum2 % range + arg_ranges[i].arg_min;
                testnum2 /= range;
                if (arg_ranges[i].argtype == arg_cardtype)
                {
                    nc[nc_idx].position = -1;
                    nc[nc_idx].cardtype = cardtypes[i] = args[i];
                    nc_idx++;
                }
            }
        }
        
        carddesc_t desc;
        set_descriptions (num_nc, nc, desc);
        
        struct gameState G;
        expectedState_t expected;
        
        setup_test_game (num_nc, nc, &G, &expected);
        
        test_callback (args, cardtypes, desc, &G, &expected);
        
        log_flush ();
        
        skip_test:;
    }
}

// See http://www.cplusplus.com/reference/cstdlib/qsort/
static int compare (const void * a, const void * b)
{
    return *(const int*)a - *(const int*)b;
}

static int compare_needed (const void * a, const void * b)
{
    return  ((const needed_card_t*)a)->position -
            ((const needed_card_t*)b)->position;
}

// Set up a test game and gain the "needed cards" into the first player's hand
// in the specified positions within the hand. It's useful to be able to
// specify the positions, because some bugs depend on card position (for
// example, the salvager bug which would stop the salvager card from working 
// when trashing the card in position one.) Use a position of -1 to indicate
// that you don't actually want the card added to the player's hand, you just
// want it in the supply.
void setup_test_game (  int num_needed_cards,
                        const needed_card_t *needed_cards,
                        struct gameState *g, expectedState_t *expected )
{
    int k[NUM_SUPPLY];
    int nk = 0;

    // Needed cards must be sorted in ascending order of desired position 
    // within the hand.
    size_t nc_size = num_needed_cards * sizeof(needed_card_t);
    needed_card_t *needed_sorted = malloc (nc_size);
    memcpy (needed_sorted, needed_cards, nc_size);
    qsort ( needed_sorted, num_needed_cards, sizeof(needed_card_t),
            compare_needed );
    
    memset (expected, 0, sizeof(*expected));
    expected->count[copper] = 7;
    expected->count[estate] = 3;
    
    // Pick the kingdom cards types for the supply.
    for (int i = 0; i < num_needed_cards; i++)
    {
        int j;
        int card = needed_sorted[i].cardtype;
        
        if (card < adventurer)
            continue;
        
        for (j = 0; j < i; j++)
        {
            if (needed_sorted[j].cardtype == card)
                break;
        }
        
        if (j != i)
            continue;
        
        // We need to be able to gain this card type, so it must be in one of
        // the supply's kingdom card slots.
        k[nk++] = card;
    }
    
    qsort (k, nk, sizeof(int), compare);
    
    // Just fill in the rest of the kingdom card slots with whatever, it won't
    // matter what as long as they're unique.
    for (int i = nk; i < NUM_SUPPLY; i++)
    {
        int which_kingdom = adventurer;
        for (int j = 0; j < i; j++)
        {
            if (which_kingdom >= k[j])
                which_kingdom++;
        }
        k[nk++] = which_kingdom;
        qsort (k, nk, sizeof(int), compare);
    }
    
    initializeGame(2, k, 1, g);
    
    int player = g->whoseTurn;
    
    // Gain all the cards that will be needed for the test, then put the cards
    // in the designated positions so the unit test knows where to look for
    // them.
    int num_not_added = 0; 
    for (int i = 0; i < num_needed_cards; i++)
    {
        int card = needed_sorted[i].cardtype;
        int position = needed_sorted[i].position;
        
        expected->isOfInterest[card] = true;
        
        // Negative position indicates that we don't actually want to add this
        // card to the player's hand
        if (position < 0)
        {
            num_not_added++;
            continue;
        }
        
        expected->count[card]++;
        
        assert (position < 5 + num_needed_cards - num_not_added);
        for (int j = 0; j < i; j++)
            assert (needed_sorted[j].position < position);
        
        assert (gainCard (card, g, 2, player) != -1);
        assert (g->hand[player][g->handCount[player]-1] == card);
        
        g->hand[player][g->handCount[player]-1] = g->hand[player][position];
        g->hand[player][position] = card;
    }
    
    // We supply the "setup_test_game" as the function name, since if this
    // fails, it's a failure of the test library code, not the unit test 
    // invoking it.
    verifyGameState_fromFunc (0, g, expected, "setting up test", __func__);
    assert (!verifyGameState_failed);
    // Erase the logs from verifyGameState if it succeeded, since it's not
    // relevant to the unit test.
    turn_log[0] = '\0'; 
}

void parse_args (int argc, char *argv[])
{
    char c;
    
    verbose = false;
    
    printf ("RUNNING %s\n", argv[0]);
    
    while ((c = getopt (argc, argv, "v")) != -1)
    {
        switch (c)
        {
            case 'v':
                verbose = true;
                break;
        }
    }
}
